﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Flower.Logging;

namespace Flower.Processor
{
    internal sealed class ProcessorsCache : IDisposable
    {
        private struct CachedProcessor
        {
            public Processing.Processor Processor;
            public DateTime PutToCacheAt;
        }
        
        private static readonly Log Log = LogManager.CreateLog(typeof(ProcessorsCache).Name);
        private static readonly TimeSpan ExpirationCheckInterval = TimeSpan.FromSeconds(1);

        private readonly ProcessorSettings _setting = 
            ApplicationContext.GetProcessorSettings();
        private readonly ManualResetEventSlim _expirationCheckerStopEvent =
            new ManualResetEventSlim();
        private readonly Dictionary<string, CachedProcessor> _cachedProcessors =
            new Dictionary<string,CachedProcessor>();
        private readonly ReaderWriterLockSlim _cachedProcessorsLock = 
            new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        private readonly Thread _expirationCheckingThread;

        private bool _isDisposed;

        public ProcessorsCache()
        {
            _expirationCheckingThread = new Thread(CheckExpiration);
            _expirationCheckingThread.Start();
        }

        private void CheckExpiration(object state)
        {
            Log.Trace("Expiration checking thread started.");
            
            var expiredPids = new List<string>();

            while (!_expirationCheckerStopEvent.Wait(ExpirationCheckInterval))
            {
                var now = DateTime.Now;
                
                _cachedProcessorsLock.EnterReadLock();
                try
                {
                    expiredPids.AddRange
                    (
                        _cachedProcessors
                            .Values
                            .Where(prc => now - prc.PutToCacheAt >= _setting.ProcessUnloadTimeout)
                            .Select(prc => prc.Processor.Pid)
                    );
                }
                finally
                {
                    _cachedProcessorsLock.ExitReadLock();
                }

                if (expiredPids.Count > 0)
                {
                    Log.Trace(() => string.Format("Cached processor(s) {0} have expired.", string.Join(", ", expiredPids)));

                    _cachedProcessorsLock.EnterWriteLock();
                    try
                    {
                        foreach (var expiredPid in expiredPids)
                        {
                            _cachedProcessors.Remove(expiredPid);
                        }
                    }
                    finally
                    {
                        _cachedProcessorsLock.ExitWriteLock();
                        expiredPids.Clear();
                    }
                }
            }
        }
        
        public Processing.Processor Take(string pid)
        {
            if (_isDisposed) throw new ObjectDisposedException("ProcessorsCache is disposed.");

            _cachedProcessorsLock.EnterWriteLock();
            try
            {
                CachedProcessor result;

                if (_cachedProcessors.TryGetValue(pid, out result))
                {
                    Log.Trace(() => string.Format("Got processor {0} from the cache.", pid));

                    _cachedProcessors.Remove(pid);
                    return result.Processor;
                }

                return null;
            }
            finally
            {
                _cachedProcessorsLock.ExitWriteLock();
            }
        }

        public void Put(Processing.Processor processor)
        {
            if (_isDisposed) throw new ObjectDisposedException("ProcessorsCache is disposed.");

            _cachedProcessorsLock.EnterWriteLock();
            try
            {
                Log.Trace(() => string.Format("Putting processor {0} to the cache.", processor.Pid));

                _cachedProcessors.Add
                (
                    processor.Pid, 
                    new CachedProcessor 
                    {
                        Processor = processor, 
                        PutToCacheAt = DateTime.Now
                    }
                );
            }
            finally
            {
                _cachedProcessorsLock.ExitWriteLock();
            }
        }

        public void Dispose()
        {
            if (!_isDisposed)
            {
                _expirationCheckerStopEvent.Set();
                _expirationCheckingThread.Join();

                _isDisposed = true;
            }

            GC.SuppressFinalize(this);
        }
    }
}
